﻿namespace WMS6.Web
{
    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;

    public class RijndaelEnhanced
    {
        private ICryptoTransform decryptor;
        private static string DEFAULT_HASH_ALGORITHM = "SHA1";
        private static int DEFAULT_KEY_SIZE = 0x100;
        private static int DEFAULT_MAX_SALT_LEN = 8;
        private static int DEFAULT_MIN_SALT_LEN = MIN_ALLOWED_SALT_LEN;
        private ICryptoTransform encryptor;
        private static int MAX_ALLOWED_SALT_LEN = 0xff;
        private int maxSaltLen;
        private static int MIN_ALLOWED_SALT_LEN = 4;
        private int minSaltLen;

        public RijndaelEnhanced(string passPhrase) : this(passPhrase, null)
        {
        }

        public RijndaelEnhanced(string passPhrase, string initVector) : this(passPhrase, initVector, -1)
        {
        }

        public RijndaelEnhanced(string passPhrase, string initVector, int minSaltLen) : this(passPhrase, initVector, minSaltLen, -1)
        {
        }

        public RijndaelEnhanced(string passPhrase, string initVector, int minSaltLen, int maxSaltLen) : this(passPhrase, initVector, minSaltLen, maxSaltLen, -1)
        {
        }

        public RijndaelEnhanced(string passPhrase, string initVector, int minSaltLen, int maxSaltLen, int keySize) : this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, null)
        {
        }

        public RijndaelEnhanced(string passPhrase, string initVector, int minSaltLen, int maxSaltLen, int keySize, string hashAlgorithm) : this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, hashAlgorithm, null)
        {
        }

        public RijndaelEnhanced(string passPhrase, string initVector, int minSaltLen, int maxSaltLen, int keySize, string hashAlgorithm, string saltValue) : this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, hashAlgorithm, saltValue, 1)
        {
        }

        public RijndaelEnhanced(string passPhrase, string initVector, int minSaltLen, int maxSaltLen, int keySize, string hashAlgorithm, string saltValue, int passwordIterations)
        {
            this.minSaltLen = -1;
            this.maxSaltLen = -1;
            this.encryptor = null;
            this.decryptor = null;
            if (minSaltLen < MIN_ALLOWED_SALT_LEN)
            {
                this.minSaltLen = DEFAULT_MIN_SALT_LEN;
            }
            else
            {
                this.minSaltLen = minSaltLen;
            }
            if ((maxSaltLen < 0) || (maxSaltLen > MAX_ALLOWED_SALT_LEN))
            {
                this.maxSaltLen = DEFAULT_MAX_SALT_LEN;
            }
            else
            {
                this.maxSaltLen = maxSaltLen;
            }
            if (keySize <= 0)
            {
                keySize = DEFAULT_KEY_SIZE;
            }
            if (hashAlgorithm == null)
            {
                hashAlgorithm = DEFAULT_HASH_ALGORITHM;
            }
            else
            {
                hashAlgorithm = hashAlgorithm.ToUpper().Replace("-", "");
            }
            byte[] initVectorBytes = null;
            byte[] saltValueBytes = null;
            if (initVector == null)
            {
                initVectorBytes = new byte[0];
            }
            else
            {
                initVectorBytes = Encoding.ASCII.GetBytes(initVector);
            }
            if (saltValue == null)
            {
                saltValueBytes = new byte[0];
            }
            else
            {
                saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
            }
            byte[] keyBytes = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations).GetBytes(keySize / 8);
            RijndaelManaged symmetricKey = new RijndaelManaged();
            if (initVectorBytes.Length == 0)
            {
                symmetricKey.Mode = CipherMode.ECB;
            }
            else
            {
                symmetricKey.Mode = CipherMode.CBC;
            }
            this.encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
            this.decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
        }

        private byte[] AddSalt(byte[] plainTextBytes)
        {
            if ((this.maxSaltLen == 0) || (this.maxSaltLen < this.minSaltLen))
            {
                return plainTextBytes;
            }
            byte[] saltBytes = this.GenerateSalt();
            byte[] plainTextBytesWithSalt = new byte[plainTextBytes.Length + saltBytes.Length];
            Array.Copy(saltBytes, plainTextBytesWithSalt, saltBytes.Length);
            Array.Copy(plainTextBytes, 0, plainTextBytesWithSalt, saltBytes.Length, plainTextBytes.Length);
            return plainTextBytesWithSalt;
        }

        public string Decrypt(byte[] cipherTextBytes)
        {
            return Encoding.UTF8.GetString(this.DecryptToBytes(cipherTextBytes));
        }

        public string Decrypt(string cipherText)
        {
            return this.Decrypt(Convert.FromBase64String(cipherText));
        }

        public byte[] DecryptToBytes(string cipherText)
        {
            return this.DecryptToBytes(Convert.FromBase64String(cipherText));
        }

        public byte[] DecryptToBytes(byte[] cipherTextBytes)
        {
            byte[] decryptedBytes = null;
            byte[] plainTextBytes = null;
            int decryptedByteCount = 0;
            int saltLen = 0;
            MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
            decryptedBytes = new byte[cipherTextBytes.Length];
            lock (this)
            {
                CryptoStream cryptoStream = new CryptoStream(memoryStream, this.decryptor, CryptoStreamMode.Read);
                decryptedByteCount = cryptoStream.Read(decryptedBytes, 0, decryptedBytes.Length);
                memoryStream.Close();
                cryptoStream.Close();
            }
            if ((this.maxSaltLen > 0) && (this.maxSaltLen >= this.minSaltLen))
            {
                saltLen = (((decryptedBytes[0] & 3) | (decryptedBytes[1] & 12)) | (decryptedBytes[2] & 0x30)) | (decryptedBytes[3] & 0xc0);
            }
            plainTextBytes = new byte[decryptedByteCount - saltLen];
            Array.Copy(decryptedBytes, saltLen, plainTextBytes, 0, decryptedByteCount - saltLen);
            return plainTextBytes;
        }

        public string Encrypt(string plainText)
        {
            return this.Encrypt(Encoding.UTF8.GetBytes(plainText));
        }

        public string Encrypt(byte[] plainTextBytes)
        {
            return Convert.ToBase64String(this.EncryptToBytes(plainTextBytes));
        }

        public byte[] EncryptToBytes(string plainText)
        {
            return this.EncryptToBytes(Encoding.UTF8.GetBytes(plainText));
        }

        public byte[] EncryptToBytes(byte[] plainTextBytes)
        {
            byte[] plainTextBytesWithSalt = this.AddSalt(plainTextBytes);
            MemoryStream memoryStream = new MemoryStream();
            lock (this)
            {
                CryptoStream cryptoStream = new CryptoStream(memoryStream, this.encryptor, CryptoStreamMode.Write);
                cryptoStream.Write(plainTextBytesWithSalt, 0, plainTextBytesWithSalt.Length);
                cryptoStream.FlushFinalBlock();
                byte[] cipherTextBytes = memoryStream.ToArray();
                memoryStream.Close();
                cryptoStream.Close();
                return cipherTextBytes;
            }
        }

        private int GenerateRandomNumber(int minValue, int maxValue)
        {
            byte[] randomBytes = new byte[4];
            new RNGCryptoServiceProvider().GetBytes(randomBytes);
            int seed = ((((randomBytes[0] & 0x7f) << 0x18) | (randomBytes[1] << 0x10)) | (randomBytes[2] << 8)) | randomBytes[3];
            Random random = new Random(seed);
            return random.Next(minValue, maxValue + 1);
        }

        private byte[] GenerateSalt()
        {
            int saltLen = 0;
            if (this.minSaltLen == this.maxSaltLen)
            {
                saltLen = this.minSaltLen;
            }
            else
            {
                saltLen = this.GenerateRandomNumber(this.minSaltLen, this.maxSaltLen);
            }
            byte[] salt = new byte[saltLen];
            new RNGCryptoServiceProvider().GetNonZeroBytes(salt);
            salt[0] = (byte) ((salt[0] & 0xfc) | (saltLen & 3));
            salt[1] = (byte) ((salt[1] & 0xf3) | (saltLen & 12));
            salt[2] = (byte) ((salt[2] & 0xcf) | (saltLen & 0x30));
            salt[3] = (byte) ((salt[3] & 0x3f) | (saltLen & 0xc0));
            return salt;
        }
    }
}

